package com.config;

import com.authentication.InMemoryAuthenticationManger;
import com.authentication.code.AuthenticationCodeRepository;
import com.authentication.code.SimpleAuthenticationCodeRepository;
import com.authentication.code2Token.Code2TokenAuthenticationManager;
import com.authentication.code2Token.impl.InMemoryRefreshTokenRepository;
import com.authentication.filter.*;
import com.authentication.manager.ClientAuthenticationManager;
import com.authentication.manager.SimpleAuthenticationManager;
import com.authentication.manager.TokenIntrospectionAuthenticationManager;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.oauth2.server.authorization.web.*;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.CookieRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.RequestContextFilter;

import java.time.Duration;
import java.util.Arrays;

import static com.authentication.common.Constant.SELECT_SCOPE_URL;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat.SELF_CONTAINED;

@Configuration
public class AuthorizationServerConfig {


    public static final String OAUTH_2_AUTHORIZATION_SERVER = "/oauth2/authorization/server";
    public static final String OAUTH_2_TOKEN = "/oauth2/token";
    public static final String LOGIN_PAGE = "/loginPage";

    public static final String LOGIN_AUTHENTICATION = "/login/authentication";
    public static final String INTROSPECTION_PREFIX = "/introspection";

    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxyRegistrationBean() {
        FilterRegistrationBean<DelegatingFilterProxy> result = new FilterRegistrationBean<>(delegatingFilterProxy());

        result.addUrlPatterns("/**");

        return result;
    }

    @Bean
    public DelegatingFilterProxy delegatingFilterProxy() {
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetBeanName("filterChainProxy");
        return proxy;
    }

    @Bean
    public FilterChainProxy filterChainProxy( SecurityFilterChain jwtSetEndpoint) {
        return new FilterChainProxy(
                Arrays.asList(
                        authorizationFilterChain(),
                        tokenFilterChain(),
                        loginPageGeneratingFilter(),
                        loginAuthenticationSecurityFilterChain(),
                        tokenIntrospection(),
                        jwtSetEndpoint
                )
        );

    }


    @Bean
    public SecurityFilterChain authorizationFilterChain() {


        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(OAUTH_2_AUTHORIZATION_SERVER),
                Arrays.asList(
                        new ExceptionTranslationFilter(
                                new LoginUrlAuthenticationEntryPoint(LOGIN_PAGE),
                                savingUserAuthorizationRequestWithoutLoginRequestCache()
                        ),

                        // 用于解析用户请求中的 认证授权信息到 SecurityContextHolder
                        new SecurityContextHolderFilter(
                                securityContextRepository()
                        ),

                        // 从 SecurityContextHolder 中解析用户的认证授权信息
                        // 只有 已授权(已登陆) 的用户才能继续访问
                        // 否则抛出 AccessDeniedException 异常
                        new AuthorizationFilter(
                                new AuthenticatedAuthorizationManager<>()
                        ),


                        /*
                         * 这里会把 权决定请求(同意受援/拒绝授权) 的请求参数合并到 最初的授权请求 中
                         * */
                        new SelectingOauth2ScopeFilter(
                                savingUserAuthorizationRequestWithoutLoginRequestCache(),
                                SELECT_SCOPE_URL
                        ),
                        /*
                         * 用于存储用户的授权决定请求(同意受援/拒绝授权)
                         * */
                        new RequestContextFilter(),


                        new OAuth2AuthorizationEndpointFilter(
                                /*
                                 * 这里会访问 权决定请求(同意受援/拒绝授权)
                                 * */
                                clientAuthenticationManager(),
                                OAUTH_2_AUTHORIZATION_SERVER
                        )

                )
        );

    }


    /*@Bean
    public RequestCache selectingScopesRequestCache() {
        return new CookieRequestCache();
    }*/






    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (HttpServletRequest request, HttpServletResponse response,
                AuthenticationException exception) -> {
            throw exception;
        };
    }

    @Bean
    public SecurityFilterChain tokenFilterChain() {
        AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher(OAUTH_2_TOKEN);
        return new DefaultSecurityFilterChain(
                requestMatcher,
                Arrays.asList(
                        new ExceptionTranslationFilter(
                               new HttpStatusEntryPoint(FORBIDDEN)
                        ),
//                        new RequestContextFilter(),
                        // refreshToken 换取 accessToken 需要验证 client 身份信息
                        new OAuth2ClientAuthenticationFilter(
                                new ClientAuthenticationManager(
                                        registeredClientRepository()
                                ),
                                requestMatcher
                        ),
                        /*new CodeStateAuthenticationFilter(
                                authenticationCodeRepository()
                        ),*/

                        new OAuth2TokenEndpointFilter(code2TokenClientAuthenticationManager(), OAUTH_2_TOKEN)
                )
        );
    }

    @Bean
    public AuthenticationManager code2TokenClientAuthenticationManager() {
        return new Code2TokenAuthenticationManager(
                authenticationCodeRepository(),
                new InMemoryRefreshTokenRepository()
        );
    }

    /**
     * 生成登录页面
     *
     * @return
     */
    @Bean
    public SecurityFilterChain loginPageGeneratingFilter() {
        DefaultLoginPageGeneratingFilter generatingFilter = new DefaultLoginPageGeneratingFilter();

        generatingFilter.setLoginPageUrl(LOGIN_PAGE);
        generatingFilter.setAuthenticationUrl(LOGIN_AUTHENTICATION);
        generatingFilter.setFormLoginEnabled(true);
        generatingFilter.setUsernameParameter("userName");
        generatingFilter.setPasswordParameter("password");
        /*generatingFilter.setResolveHiddenInputs(request->{
            return request.getParameterMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,(Map.Entry<String,String[]>entry)->{
                String[] value = entry.getValue();
                if(value!=null&&value.length>=1){
                    return value[0];
                }
                return "";
            }));
        });*/


        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(LOGIN_PAGE),
                generatingFilter
        );
    }

    /**
     * 执行登陆认证的过滤器链
     *
     * @return
     */
    @Bean
    public SecurityFilterChain loginAuthenticationSecurityFilterChain() {

        AuthenticationFilter authenticationFilter = new AuthenticationFilter(
                userAuthenticationManager(),
                request -> {
                    return new UsernamePasswordAuthenticationToken(request.getParameter("userName"), request.getParameter("password"));
                }
        );

        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();


        successHandler.setRequestCache(savingUserAuthorizationRequestWithoutLoginRequestCache());

        authenticationFilter.setSuccessHandler(
                successHandler
                /*(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication)->{

                }*/
        );

        authenticationFilter.setSecurityContextRepository(
                securityContextRepository()
        );

        authenticationFilter.setFailureHandler(authenticationFailureHandler());


        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(LOGIN_AUTHENTICATION),
                Arrays.asList(
                        authenticationFilter,
                        new ReturnFilter()
                )

        );
    }

    /**
     * 如果用户在 oauth2 授权 client 的时候没有登陆
     * 那么保存 oauth2 授权 client 的请求信息
     * 当 user 登陆成功之后,再把用户重定向到 授权 client 的地址
     *
     * @return
     */
    @Bean
    public RequestCache savingUserAuthorizationRequestWithoutLoginRequestCache() {

        return new CookieRequestCache();
    }


    @Bean
    public AuthenticationManager clientAuthenticationManager() {


        return new InMemoryAuthenticationManger(
                authenticationCodeRepository()
        );
    }

    @Bean
    public AuthenticationCodeRepository authenticationCodeRepository() {
        return new SimpleAuthenticationCodeRepository();

    }

    @Bean
    public AuthenticationManager userAuthenticationManager() {
        return new SimpleAuthenticationManager();
    }

    /**
     * user 登陆成功之后 存储用户认证授权信息的仓库
     * 当请求抵达时,此仓库解析请求中的认证授权信息
     * @return
     */
    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }

    /*@Bean
    public OAuth2TokenGenerator<?> tokenGenerator() {
        JwtEncoder jwtEncoder = new NimbusJwtEncoder();
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }*/

    @Bean
    public RegisteredClientRepository registeredClientRepository() {

        TokenSettings settings = TokenSettings.builder()
                // 配置使用jwt令牌
                .accessTokenFormat(SELF_CONTAINED)
                .accessTokenTimeToLive(Duration.ofSeconds(3))
                .refreshTokenTimeToLive(Duration.ofMinutes(5))
                .build();

        RegisteredClient client = RegisteredClient.withId("0001")
                .clientId("pdd")
                .clientSecret("pdd_password")
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://localhost:7000/oauth2/swapCodeToToken")
                .tokenSettings(settings)
                .clientName("拼多多百亿补贴")
                .scope("user_id")
                .scope("read_birthday")
                .scope("friends_list")
                .build();

        return new InMemoryRegisteredClientRepository(client);
    }


    /**
     * 用于 resource server 来验证 access token 的有效性
     *
     * @return
     */
    @Bean
    public SecurityFilterChain tokenIntrospection() {

        String tokenIntrospectionEndpointUri = INTROSPECTION_PREFIX + "/access/token";
        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher(tokenIntrospectionEndpointUri),
                Arrays.asList(
                        new ResourceServerAuthenticationFilter(),
                        new OAuth2TokenIntrospectionEndpointFilter(
                                new TokenIntrospectionAuthenticationManager(),
                                tokenIntrospectionEndpointUri
                        )

                )

        );
    }

    /**
     * 用于 resource server 获取 jwt token 的验证信息来校验 token是否有效
     *
     * @return
     */
    @Bean
    public SecurityFilterChain jwtSetEndpoint(JWKSource<SecurityContext> randomRsaJwkSource) {

        String url = "/introspection/jwkSet";
        NimbusJwkSetEndpointFilter nimbusJwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
                randomRsaJwkSource,
                url
        );


        OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter = new OAuth2AuthorizationServerMetadataEndpointFilter();


        authorizationServerMetadataEndpointFilter.setAuthorizationServerMetadataCustomizer(builder -> {
            builder.jwkSetUrl(url);
        });

        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/**"),
                Arrays.asList(
                        authorizationServerMetadataEndpointFilter,
                        nimbusJwkSetEndpointFilter
                )

        );
    }
}
